(function(global,factory){
    typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
    typeof define === 'function' && define.amd ? define(factory) :
    (global = global || self, global.Router = factory());
}(this,function(){
    let dftXHR = {
        url: null// 请求地址
        ,method: 'POST'// 请求类型
        ,data: {}// 传递数据
        ,async: true// 是否异步执行
        ,dataType:''//''默认值text
        ,contentType:''
        ,header:{
            // 设置请求主体的数据类型:'application/json;charset=utf-8'
            'Content-Type':'application/x-www-form-urlencoded; charset=utf-8'
        }
        ,progress:function(){}
        ,before: function(){}// 请求发送前执行事件
        ,complete: function(){}// 请求发送后执行事件
        ,success: function(){}// 请求成功后执行事件
        ,fail: function(){}// 请求失败后执行事件
    }
    , dftSetting = {
        context:'.'
        ,el:'#app'
        ,layout:''
        ,routerView: '#app-body'
        ,tabContainer:''
        ,beforeInclude:function(){}
        ,included:function(){}
    }
    ,hasProto = '__proto__' in {}

    , Router = function(options){
        //缓存route数据信息
        this.routes = {};
        this.options = Object.assign({},dftSetting,options);
        this.customEvent = new Router.CustomEvt();
        this.ready();
    }

    ,$ = function(selector){
        return new initDOM(selector);
    }

    ,initDOM = function(){
        if(!selector){return this}
        let dataTypes = {
            'string':function(){
                this.selector = selector;
                saveDom.call(this,this.queryAll(this.context,selector))
            }
            ,'object':function(){
                saveDom.call(this,selector)
            }
            ,'el':function(){
                this[0] = selector
                this.length = 1;
            }
        }
        ,saveDom = function(nodeList){
            Object.assign(this,nodeList)
            this.length = nodeList && nodeList.length || 0;
        }
        this.context = context||document;
        dataTypes[selector.nodeType ? 'el' : typeof(selector)].call(this)
        return this;
    } 

    ,CustomEvt = function(){
        this.subEvents = {};
    }

    $.extend = function(isDeep,target){
        let args = Array.from(arguments);
        if(typeof isDeep != 'boolean'){
            return Object.assign.apply(Object,args);
        }
        let deepClone = function(target,obj){
            Object.keys(obj).forEach(key=>{
                let value = obj[key];
                if(typeof value == 'object'){
                    let newObj = Array.isArray(value) ? target[key]||[] : target[key]||{};
                    target[key] = deepClone(newObj,value)
                }else{
                    target[key] = value;
                }
            })
            return target;
        }
        args.slice(2).forEach(obj=>{
            deepClone(target,obj);
        })
        return target;
    }

    CustomEvt.prototype = {
        constructor:CustomEvt
        ,$emit:function(event,data){
            event = this.subEvents[event];
            if(event){
                for(let i = 0; i < event.length; i++){
                    event[i].apply(this, data);
                }
            }
        }
        ,$on:function(event,handle){
            let subEvents = this.subEvents;
            !(event in subEvents) && (subEvents[event] = [])
            subEvents[event].push(handle);
        }
        ,$off:function(event,handle){
            let events = this.subEvents[event];
            if(!handle){
                events && delete events
            }else{
                if(typeof handle == 'function' && events){
                    for(let i = 0,len = events.length; i < len; i++){
                        let event = events[i];
                        if(event == handle){
                            return events.splice(i,1);
                        }
                    } 
                }
            }
        }
    }

    Router.utils = {
        arrayLike:function(arrLike){
            return Array.from ? Array.from(arrLike) : Array.prototype.slice.call(arrLike);
        }
        ,isArrLike:function(el){
            return el.length && /^\d+$/.test(Object.getOwnPropertyNames(el).join('')) ? true : false;
        }
        ,isDOM:function(el){
            let domNodeVal = [Node.ELEMENT_NODE,Node.DOCUMENT_NODE]; 
            return domNodeVal.indexOf(el.nodeType) > -1 ? true : false;
        }
        ,getEl:function(selector){
            if(!this.isDOM(selector)){
                if(/^#[^#\s\b]+$/.test(selector)){
                    selector = document.querySelector(selector)
                }else{
                    selector = document.querySelectorAll(selector)
                }
            }
            return selector;
        }
        ,animationEnd:function(ele,sucHandle){
            //1、获取动画名
            let animateName = function(){
                const animation = {
                    'animation': 'animationend',
                    'OAnimation': 'oAnimationEnd',
                    'MozAnimation': 'mozAnimationEnd',
                    'WebkitAnimation': 'webkitAnimationEnd'
                }
                const animateName = (function(el) {
                    for (let t in animation) {
                        if (el.style[t] !== undefined) {
                            return animation[t];
                        }
                    }
                })(document.createElement('div'));
                return animateName;
            }();
            //2、给ele绑定动画结束的回调
            const animateEndEventListener = function(sucHandle){
                //在每次transitionEnd的事件后执行该函数
                const handleAniEnd = function(e){
                    ele.removeEventListener(animateName,handleAniEnd);
                    sucHandle.call(this,e,ele);
                };
                ele.addEventListener(animateName,handleAniEnd,false);
            }
            animateName && animateEndEventListener(sucHandle)
        }
        ,evtListener:function(el,event,handle){
            el = typeof el == 'string' && this.getEl(el);
            Array.prototype.forEach.call(el,function(el){
                el.addEventListener(event,handle)
            });
        }
        ,removeListener:function(el,event,handle){
            el = Router.isDOM(el) ? [...el] : [el];
            el.forEach(el=>el.removeEventListener(event,handle));
        }
        ,isHash:function(val){
            return /#[^#.]+/.test(val)
        }
        ,sendxhr:function(options){
            return new Promise(function(resolve,reject){
                let setting = Object.assign({},dftXHR,options)
                ,xhr = new XMLHttpRequest()
                ,method = setting.method.toUpperCase()
                ,getQueryStr = method == 'GET' && /[^?]+(\?[\s\S]+)?/.exec(setting.url)[1] || ''
                ,setParams = function(){
                    xhr.timeout = 25000;
                    //设置想要的数据类型
                    setting.dataType && (xhr.responseType = setting.dataType)
                    xhr.open(method,setting.url+getQueryStr,setting.async)
                    // 此方法需要在open方法和send方法之间调用
                    let header = setting.header
                    for(let key in header){
                        xhr.setRequestHeader(key,header[key]);
                    }
                    method == 'POST' && !'Content-Type' in header &&  xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded; charset=utf-8')
                }()   
                ,evtListener = function(){
                    xhr.addEventListener('loadstart', function(){ 
                        setting.before(xhr);
                    });
                    //上传进度
                    xhr.addEventListener('progress',function(e){
                        //长度可计算并且readyState == '3'
                        if(e.lengthComputable){
                            setting.progress.call(xhr,e,e.loaded,e.total)
                        }
                    })
        
                    //请求成功的事件
                    //监听onreadystatechange并执行成功失败事件
                    //readyState可以用onload代替
                    xhr.addEventListener('readystatechange',function(){
                        if(xhr.readyState === 4 && (xhr.status >= 200 && xhr.status < 300 || xhr.status==304)){
                            setting.success.call(xhr,xhr.response,xhr.status);
                            resolve.call(xhr,xhr.response,xhr.status);
                        }
                    })
        
                    //请求失败回调
                    xhr.addEventListener('error',function(){ 
                        setting.fail.call(xhr,xhr.status,xhr.statusText);
                        reject.call(xhr,xhr.status,xhr.statusText);
                    })
    
                    xhr.addEventListener('timeout',function(){
                        setting.fail.call(xhr,xhr.status,xhr.statusText);
                        reject.call(xhr,xhr.status,xhr.statusText);
                        xhr.abort();
                    })
        
                    //无论请求成功还是失败都会执行的事件，
                    xhr.addEventListener('loadend',function(){
                        setting.complete.call(xhr,xhr.status,xhr.statusText);
                    })
                }()
                //请求主体数据
                ,assembleQueryStr = function(obj){
                    if(setting.contentType && setting.contentType.indexOf("application/json") == 0){
                        return JSON.stringify(setting.data);
                    }else{
                        let args = "";
                        Object.keys(obj).forEach(function(key){
                            let val = obj[key]||''
                            args += key+"="+decodeURIComponent(val)+"&";
                        });
                        return args.substring(0,args.length-1);
                    }
                }
                xhr.send(assembleQueryStr(setting.data));
            })
        }
        ,evalScripts:function(text){
            let script
            , regexp = /<script(?:\s+src=('|")([\w.-/?]+?)\1)?\s*>\s*([\s\S]*?)\s*<\/script>/gi
            , head = document.querySelectorAll('head')[0]
            , createScript = function(src,callback){
                let node = document.createElement('script');
                node.type = 'text/javascript';
                node.onload = node.onreadystatechange = function() {
                    if(!/*@cc_on!@*/0 || this.readyState === 'loaded' || this.readyState === 'complete') {
                        this.onload = this.onreadystatechange = null; 
                        this.parentNode.removeChild(this);
                        typeof callback == 'function' && callback()
                    }
                }
                node.src = src;
                return node;
            }
            ,serializeScript = function(){
                script = regexp.exec(text);
                if(!script){return}
                if(script[2]){
                    let node = createScript(script[2],function(){
                        serializeScript(text)
                    })
                    head.appendChild(node)
                }else{
                    window.execScript ? execScript(script[3]) : eval(script[3])
                    serializeScript(text)
                }
            }
            serializeScript();
        }
        ,setTitle:function(text){
            let reg = /<title>([\s\S]*?)<\/title>/i
            ,title = document.querySelectorAll('title')[0]
            if(text.match(reg)){
                text = text.replace(reg,function(){
                    let arg = arguments;
                    title.textContent = arg[1]||'';
                    return "";
                })
            }else{
                title.textContent = '';
            }
            return text;
        }
        ,def(target,name,value,enumerable){
            Object.defineProperty(target,name,{
                enumerable: !!enumerable,
                configurable: true,
                writable: true,
                value:value
            })
        }
    }

    if(hasProto){
        Router.utils.__proto__ = Object.create(Array.prototype);
    }else{
        let orginal = Array.prototype;
        Object.getOwnPropertyNames(orginal).forEach((method)=>{
            if(method == 'length'){return}
            Router.utils.def(Router.utils,method,orginal[method]);
        })
    }

    $.extend(Router.prototype,{
        constructor:Router
        ,parse:function(text){
            if(!this.options.routerView){return}
            let utils = Router.utils
            ,regScript = /<script(?:\s+src=('|")([\w.-/]+?)\1)?\s*>\s*([\s\S]*?)\s*<\/script>/gi;
            text = utils.setTitle(text);
            utils.getEl(this.options.routerView).innerHTML = text.replace(regScript,"");
            utils.evalScripts(text);
        }
        //收集routes
        ,route:function(setting,callback){
            let _self = this,path = setting.path;
            this.routes[path] = function(){
                delete setting.path
                let handle = function(path,html){
                    if(typeof callback == 'function'){
                        callback.call(_self,html,path)
                    }else{
                        _self.parse(html,path)
                    }
                }
                setting.url && Router.utils.sendxhr(Object.assign({method:'get'},setting)).then(function(html){
                    handle(path,html)
                }) || handle(path)
            }
        }
        //重定向会改变hash
        ,redirect:function(path){
            location.hash != path && (location.hash = path)
        }
        //跳转是内部行为，不改变hash
        ,forward:function(path){
            this.routes[path] && this.routes[path].call(this)
        }
        ,init:function(firstPath){
            let curHash = location.hash;
            curHash && (firstPath = curHash)
            Router.utils.isHash(firstPath) && (location.hash = firstPath) && curHash && this.forward(firstPath)
        }
        //监听hashchange
        ,listenerHashchange:function(){
            Router.utils.evtListener(window,'hashchange',()=>{
                this.forward(location.hash)
            });
        }
        ,include:function(path){
            let _self = this
            ,utils = Router.utils
            _self.options.beforeInclude.call(_self,path)
            _self.customEvent.$emit('beforeInclude',[path])
            utils.sendxhr({url:path,method:'get'}).then(function(layout){
                let regScript = /<script(?:\s+src=('|")([\w.-/]+?)\1)?\s*>\s*([\s\S]*?)\s*<\/script>/gi;
                if(regScript.test(layout)){
                    utils.evalScripts(layout);
                    layout = layout.replace(regScript,"");
                }
                let el = utils.getEl(_self.options.el);
                el.innerHTML = layout;
                _self.options.included.call(_self,el,path);
                _self.customEvent.$emit('included',[el,layout])
            })
        }
        //构建布局
        ,build:function(){
            let path = this.options.layout;
            path && this.include(path);
        }
        //初始化
        ,ready:function(){
            this.listenerHashchange();
            this.options.layout && this.build();
        }
    })

    initDOM.prototype = Router.utils;

    Router.CustomEvt = CustomEvt;

    Router.$ = $;

    return Router;
}));